using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Threading;
using System.Runtime.InteropServices;
using Microsoft.BizTalk.Message.Interop;
using Microsoft.BizTalk.TransportProxy.Interop;
using Microsoft.XLANGs.BaseTypes;

namespace Microsoft.Samples.BizTalk.Adapter.Tcp
{
    /// <summary>
    ///     This class represents a batch containing a set of batch messages and a set of helper
    ///     functions to submit messages to batches.
    /// </summary>
    /// <author>
    ///     Paul Brown
    /// </author>
    public sealed partial class Batch : IBTBatchCallBack, IDisposable
    {
        #region Private Static Members

        private static Dictionary<string, Batch> _batches;              // An array of the batches (shared across all threads)
        private static object           _lockObject;                    // The lock object for the array of batches

        #endregion

        #region Private Instance Members

        private IAdapter                _adapter;                       // A reference to the adapter
        private bool                    _isDisposed;                    // Is this batch disposed of?
        private bool                    _isSubmitResponse;              // Is this batch used for submitting responses (send adapter)?
        private IBTTransportBatch       _batch;                         // A reference to the transport proxy batch
        private string                  _batchCookie;                   // A unique reference for the batch
        private int                     _batchStatus;                   // The status of the batch post BatchComplete
        private List<BatchMessage>      _submitMessages;                // The submitted oneway messages in the batch
        private List<BatchMessage>      _deleteMessages;                // The deleted messages in the batch
        private List<BatchMessage>      _resubmitMessages;              // The resubmitted messages in the batch
        private List<BatchMessage>      _moveToSuspendQMessages;        // The suspended messages in the batch
        private List<BatchMessage>      _moveToNextTransportMessages;   // The move to backup transport messages in the batch
        private List<BatchMessage>      _submitRequestMessages;         // The submitted request messages in the batch
        private List<BatchMessage>      _submitResponseMessages;        // The submitted response messages in the batch
        private List<BatchMessage>      _cancelResponseMessages;        // The cancelled response messages in the batch
        private bool                    _orderedBatch;                  // Specifies whether this batch should be ordered
        private AutoResetEvent          _orderedEvent;                  // A signal used to synchronize across batches for ordered delivery
        private Batch                   _failureBatch;                  // This batch is used to resubmit a failed batch
        private int                     _retryCount;                    // This determines the number of retries that will occur when handling a failed batch
        private int                     _retryInterval;                 // This determines the number of seconds to wait between retries
        private IBTBatchCallBack        _callback;                      // This is the object which implements the callback interface
        private int                     _failureDepth;                  // How many batch failures for the top level batch have occurred
        private bool                    _messagesSuspended;             // Were any messages suspended in this batch?
        private int                     _messageCount;                  // The number of messages in the batch
        private Batch                   _topLevelBatch;                 // A reference to the top level batch if this is a failure child batch
        private bool                    _batchFailed;                   // A flag that determines whether this batch failed processing

        #endregion

        #region Constructor - Static

        /// <summary>
        ///     This type constructor initializes the type.
        /// </summary>
        static Batch()
        {
            Batch._batches = new Dictionary<string, Batch>();
            Batch._lockObject = new object();
        }

        #endregion

        #region Constructor - Instance

        /// <summary>
        ///     This constructor initializes the instance.
        /// </summary>
        public Batch(IAdapter adapter)
        {
            // Validate
            if (adapter == null) throw new ArgumentNullException("adapter");

            // Initialize members
            this._adapter = adapter;
            this._isSubmitResponse = false;
            this._batchCookie = Guid.NewGuid().ToString();
            this._callback = this;
            this._batch = this.Adapter.TransportProxy.GetBatch(this._callback, this.BatchCookie);
            this._batchStatus = 0;
            this._submitMessages = null;
            this._deleteMessages = null;
            this._resubmitMessages = null;
            this._moveToSuspendQMessages = null;
            this._moveToNextTransportMessages = null;
            this._submitRequestMessages = null;
            this._submitResponseMessages = null;
            this._cancelResponseMessages = null;
            this._orderedEvent = new AutoResetEvent(false);
            this._failureBatch = null;
            this._retryCount = 5;
            this._retryInterval = 10;
            this._orderedBatch = false;
            this._failureDepth = 0;
            this._messagesSuspended = false;
            this._messageCount = 0;
            this._topLevelBatch = null;
            this._batchFailed = false;
        }

        #endregion

		#region Finalizer

		/// <summary>
		///     This method is the finalizer for the class.  It uses the same syntax as
		///     a C++ destructor but is not deterministically destroyed.  By using this
		///     syntax, the compiler automatically emits a try-finally block around the
		///     code to ensure the base class is disposed of as well.
		/// </summary>
		~Batch()
		{
			// Call private implementation of Dispose
			this.Dispose(false);
		}

		#endregion

        #region Public Instance Properties

        /// <summary>
        ///     This property returns a reference to the adapter.
        /// </summary>
        public IAdapter Adapter
        {
            get { return (this._adapter); }
        }

        /// <summary>
        ///     This property returns whether the object has been disposed of.
        /// </summary>
        public bool IsDisposed
        {
            get { return (this._isDisposed); }
        }

        /// <summary>
        ///     This batch is used to resubmit a failed batch by constructing a new batch with new operations based
        ///     on the messages in this containing batch.
        /// </summary>
        public Batch FailureBatch
        {
            get { return (this._failureBatch); }
        }

        /// <summary>
        ///     This property returns a refernce to the Transport Proxy batch.
        /// </summary>
        public IBTTransportBatch TransportBatch
        {
            get { return (this._batch); }
        }

        /// <summary>
        ///     This property returns the batch cookie used when creating the batch.
        /// </summary>
        public string BatchCookie
        {
            get { return (this._batchCookie); }
        }

        /// <summary>
        ///     This property gets or sets the batch status that is returned on BatchComplete.
        /// </summary>
        public int BatchStatus
        {
            get { return (this._batchStatus); }
            set { this._batchStatus = value; }
        }

        /// <summary>
        ///     This property gets or sets the batch retry count limit.
        /// </summary>
        public int RetryCount
        {
            get { return (this._retryCount); }
            set { this._retryCount = value; }
        }

        /// <summary>
        ///     This property gets or sets the interval in seconds between retries.
        /// </summary>
        public int RetryInterval
        {
            get { return (this._retryInterval); }
            set { this._retryInterval = value; }
        }

        /// <summary>
        ///     This property gets or sets whether this batch should be delivered in order.
        /// </summary>
        public bool OrderedBatch
        {
            get { return (this._orderedBatch); }
            set { this._orderedBatch = value; }
        }

        /// <summary>
        ///     This property returns the ordered event wait object used to ensure batches are ordered.
        /// </summary>
        public AutoResetEvent OrderedEvent
        {
            get { return (this._orderedEvent); }
        }

        /// <summary>
        ///     This property gets or sets whether any messages were suspended (by choice or by failure of submit operations)
        ///     in this batch.
        /// </summary>
        public bool MessagesSuspended
        {
            get { return (this._messagesSuspended); }
            set { this._messagesSuspended = value; }
        }

        /// <summary>
        ///     This property gets or sets the failure depth, that is, how many times the batch has failed submission.
        /// </summary>
        public int FailureDepth
        {
            get { return (this._failureDepth); }
            set { this._failureDepth = value; }
        }

        /// <summary>
        ///     This property returns the number of items in this batch.
        /// </summary>
        public int Count
        {
            get { return (this._messageCount); }
        }

        /// <summary>
        ///     This property gets or sets a reference to the top level batch (not a failure batch) or null if this is
        ///     the top level batch.
        /// </summary>
        public Batch TopLevelBatch
        {
            get { return (this._topLevelBatch); }
            set { this._topLevelBatch = value; }
        }

        /// <summary>
        ///     This property gets or sets whether this batch has failed processing.
        /// </summary>
        public bool BatchFailed
        {
            get { return (this._batchFailed); }
            set { this._batchFailed = value; }
        }

        /// <summary>
        ///     This indexer property gets the batch message at the specified index that was associated with
        ///     the specified operation type.
        /// </summary>
        public BatchMessage this[int index, BatchOperationType operationType]
        {
            get
            {
                switch (operationType)
                {
                    case BatchOperationType.Submit :
                        if (this._submitMessages == null) throw new IndexOutOfRangeException();
                        return (this._isSubmitResponse == true ? this._submitResponseMessages[index] : this._submitMessages[index]);

                    case BatchOperationType.Delete :
                        if (this._deleteMessages == null) throw new IndexOutOfRangeException();
                        return (this._deleteMessages[index]);

                    case BatchOperationType.Resubmit :
                        if (this._resubmitMessages == null) throw new IndexOutOfRangeException();
                        return (this._resubmitMessages[index]);

                    case BatchOperationType.MoveToSuspendQ :
                        if (this._moveToSuspendQMessages == null) throw new IndexOutOfRangeException();
                        return (this._moveToSuspendQMessages[index]);

                    case BatchOperationType.MoveToNextTransport :
                        if (this._moveToNextTransportMessages == null) throw new IndexOutOfRangeException();
                        return (this._moveToNextTransportMessages[index]);

                    case BatchOperationType.SubmitRequest :
                        if (this._submitRequestMessages == null) throw new IndexOutOfRangeException();
                        return (this._submitRequestMessages[index]);

                    case BatchOperationType.CancelRequestForResponse :
                        if (this._cancelResponseMessages == null) throw new IndexOutOfRangeException();
                        return (this._cancelResponseMessages[index]);

                    default :
                        throw new ArgumentException("operationType");
                }
            }
        }

        #endregion

        #region Public Static Properties

        /// <summary>
        ///     This property returns an array of batches associated with this type.
        /// </summary>
        public static Batch[] Batches
        {
            get
            {
                lock (Batch._lockObject)
                {
                    Batch[] batches = new Batch[Batch._batches.Count];
                    Batch._batches.Values.CopyTo(Batches, 0);
                    return (batches);
                }
            }
        }

        #endregion

        #region Public Static Methods

        /// <summary>
        ///     This method adds a batch to the type collection.
        /// </summary>
        public static void AddBatch(Batch batch)
        {
            lock (Batch._lockObject)
            {
                Batch._batches.Add(batch.BatchCookie, batch);
            }
        }

        /// <summary>
        ///     This method removes a batch from the type collections.
        /// </summary>
        public static void RemoveBatch(string batchCookie)
        {
            lock (Batch._lockObject)
            {
                Batch._batches.Remove(batchCookie);
            }
        }

        /// <summary>
        ///     This method gets a batch from the type collection.  It returns null if the key doesn't exist.
        /// </summary>
        public static Batch GetBatch(string batchCookie)
        {
            lock (Batch._lockObject)
            {
                Batch batch;
                Batch._batches.TryGetValue(batchCookie, out batch);
                return (batch);
            }
        }

        #endregion

        #region Public Instance Methods

        /// <summary>
        ///     This method clears the batch of all messages.
        /// </summary>
        public void Clear()
        {
            this.TransportBatch.Clear();
            if (this._submitMessages != null) this._submitMessages.Clear();
            if (this._deleteMessages != null) this._deleteMessages.Clear();
            if (this._resubmitMessages != null) this._resubmitMessages.Clear();
            if (this._moveToSuspendQMessages != null) this._moveToSuspendQMessages.Clear();
            if (this._moveToNextTransportMessages != null) this._moveToNextTransportMessages.Clear();
            if (this._submitRequestMessages != null) this._submitRequestMessages.Clear();
            if (this._submitResponseMessages != null) this._submitResponseMessages.Clear();
            if (this._cancelResponseMessages != null) this._cancelResponseMessages.Clear();
            this.MessagesSuspended = false;
            this._messageCount = 0;
        }

        /// <summary>
        ///     This method submits a one-way message.
        /// </summary>
        public void SubmitMessage(BatchMessage message)
        {
            // Validate arguments
            if (message == null) throw new ArgumentNullException("message");

            try
            {
                // Submit to batch
                this.TransportBatch.SubmitMessage(message.Message);
                message.BatchOperationType = BatchOperationType.Submit;
            }
            catch (COMException e)
            {
                TraceHelper.WriteEntry(TraceLevel.Error, ResourceConstants.BizTalkBatchMethodFailure, "SubmitMessage", this.BatchCookie, e.ErrorCode, e.Message);

                // Failed to submit, suspend message instead
                message.MessageException = e;
                this.MoveToSuspendQ(message);
                return;
            }

            if (this._submitMessages == null)
            {
                // Create array
                this._submitMessages = new List<BatchMessage>();
            }

            // Add to array
            this._submitMessages.Add(message);

            // Set count and message order
            this._messageCount++;
            message.MessageOrder = this._messageCount;
        }

        /// <summary>
        ///     This method deletes a message held by BizTalk.
        /// </summary>
        public void DeleteMessage(BatchMessage message)
        {
            // Validate arguments
            if (message == null) throw new ArgumentNullException("message");

            try
            {
                // Submit to batch
                this.TransportBatch.DeleteMessage(message.Message);
                message.BatchOperationType = BatchOperationType.Delete;
            }
            catch (COMException e)
            {
                // Failed - BizTalk documentation states that this is not expected to fail so no corrective action needed
                TraceHelper.WriteEntry(TraceLevel.Error, ResourceConstants.BizTalkBatchMethodFailure, "DeleteMessage", this.BatchCookie, e.ErrorCode, e.Message);
            }

            if (this._deleteMessages == null)
            {
                // Create array
                this._deleteMessages = new List<BatchMessage>();
            }

            // Add to array
            this._deleteMessages.Add(message);

            // Set count and message order
            this._messageCount++;
            message.MessageOrder = this._messageCount;
        }

        /// <summary>
        ///     This method requests that a message be retried later.
        /// </summary>
        public void ResubmitMessage(BatchMessage message)
        {
            // Validate arguments
            if (message == null) throw new ArgumentNullException("message");

            try
            {
                // Get retry count and interval from context
                SystemMessageContext context = new SystemMessageContext(message.Message.Context);

                if (context != null && context.RetryCount > 0)
                {
                    // Submit to batch
                    message.Message.SetErrorInfo(message.MessageException);
                    this.TransportBatch.Resubmit(message.Message, DateTime.Now.AddMinutes(context.RetryInterval));
                    message.BatchOperationType = BatchOperationType.Resubmit;
                }
                else
                {
                    // Move to next transport
                    this.TransportBatch.MoveToNextTransport(message.Message);
                    message.BatchOperationType = BatchOperationType.MoveToNextTransport;
                }
            }
            catch (COMException e)
            {
                // Failed - BizTalk documentation states that this is not expected to fail so no corrective action needed
                TraceHelper.WriteEntry(TraceLevel.Error, ResourceConstants.BizTalkBatchMethodFailure, "ResubmitMessage", this.BatchCookie, e.ErrorCode, e.Message);
            }

            if (this._resubmitMessages == null)
            {
                // Create array
                this._resubmitMessages = new List<BatchMessage>();
            }

            // Add to array
            this._resubmitMessages.Add(message);

            // Set count and message order
            this._messageCount++;
            message.MessageOrder = this._messageCount;
        }

        /// <summary>
        ///     This method moves the message to the suspend queue.
        /// </summary>
        public void MoveToSuspendQ(BatchMessage message)
        {
            // Validate arguments
            if (message == null) throw new ArgumentNullException("message");

            try
            {
                // Submit to batch
                message.Message.SetErrorInfo(message.MessageException);
                this.TransportBatch.MoveToSuspendQ(message.Message);
                message.BatchOperationType = BatchOperationType.MoveToSuspendQ;
                this.MessagesSuspended = true;
            }
            catch (COMException ex)
            {
                // Failed - BizTalk documentation states that this is not expected to fail so no corrective action needed
                TraceHelper.WriteEntry(TraceLevel.Error, ResourceConstants.BizTalkBatchMethodFailure, "MoveToSuspendQ", this.BatchCookie, ex.ErrorCode, ex.Message);
            }

            if (this._moveToSuspendQMessages == null)
            {
                // Create array
                this._moveToSuspendQMessages = new List<BatchMessage>();
            }

            // Add to array
            this._moveToSuspendQMessages.Add(message);

            // Set count and message order
            this._messageCount++;
            message.MessageOrder = this._messageCount;
        }

        /// <summary>
        ///     This method moves the message to the backup transport.
        /// </summary>
        public void MoveToBackupTransport(BatchMessage message)
        {
            // Validate arguments
            if (message == null) throw new ArgumentNullException("message");

            try
            {
                // Submit to batch
                message.Message.SetErrorInfo(message.MessageException);
                this.TransportBatch.MoveToNextTransport(message.Message);
                message.BatchOperationType = BatchOperationType.MoveToNextTransport;
            }
            catch (COMException e)
            {
                TraceHelper.WriteEntry(TraceLevel.Error, ResourceConstants.BizTalkBatchMethodFailure, "MoveToNextTransport", this.BatchCookie, e.ErrorCode, e.Message);

                // Failed, move to suspend queue
                message.MessageException = e;
                this.MoveToSuspendQ(message);
                return;
            }

            if (this._moveToNextTransportMessages == null)
            {
                // Create array
                this._moveToNextTransportMessages = new List<BatchMessage>();
            }

            // Add to array
            this._moveToNextTransportMessages.Add(message);

            // Set count and message order
            this._messageCount++;
            message.MessageOrder = this._messageCount;
        }

        /// <summary>
        ///     This method submits the request message of a request-response pair.
        /// </summary>
        public void SubmitRequestMessage(BatchMessage message)
        {
            // Validate arguments
            if (message == null) throw new ArgumentNullException("message");

            try
            {
                // Submit to batch
                this.TransportBatch.SubmitRequestMessage(message.Message, message.CorrelationToken, true, message.ResponseMessageExpiration, message.TransmitterCallback);
                message.BatchOperationType = BatchOperationType.SubmitRequest;
            }
            catch (COMException e)
            {
                TraceHelper.WriteEntry(TraceLevel.Error, ResourceConstants.BizTalkBatchMethodFailure, "SubmitRequestMessage", this.BatchCookie, e.ErrorCode, e.Message);

                // Failed to submit, move to suspend queue instead
                message.MessageException = e;
                this.MoveToSuspendQ(message);
                return;
            }

            if (this._submitRequestMessages == null)
            {
                // Create array
                this._submitRequestMessages = new List<BatchMessage>();
            }

            // Add to array
            this._submitRequestMessages.Add(message);

            // Set count and message order
            this._messageCount++;
            message.MessageOrder = this._messageCount;
        }

        /// <summary>
        ///     This method submits the response message of a request-response pair.
        /// </summary>
        public void SubmitResponseMessage(BatchMessage message)
        {
            // Validate arguments
            if (message == null) throw new ArgumentNullException("message");

            try
            {
                // Submit to batch
                this.TransportBatch.SubmitResponseMessage(message.SolicitMessage, message.Message);
                message.BatchOperationType = BatchOperationType.Submit;
            }
            catch (COMException e)
            {
                TraceHelper.WriteEntry(TraceLevel.Error, ResourceConstants.BizTalkBatchMethodFailure, "SubmitResponseMessage", this.BatchCookie, e.ErrorCode, e.Message);

                // Failed to submit, move to suspend queue instead
                message.MessageException = e;
                this.MoveToSuspendQ(message);
                return;
            }

            if (this._submitResponseMessages == null)
            {
                // Create array
                this._submitResponseMessages = new List<BatchMessage>();
            }

            // Add to array
            this._submitResponseMessages.Add(message);
            this._isSubmitResponse = true;

            // Set count and message order
            this._messageCount++;
            message.MessageOrder = this._messageCount;
        }

        /// <summary>
        ///     This method informs the messaging engine that the adapter no longer needs to wait for the response message.
        /// </summary>
        public void CancelResponseMessage(BatchMessage message)
        {
            // Validate arguments
            if (message == null) throw new ArgumentNullException("message");

            try
            {
                // Submit to batch
                this.TransportBatch.CancelResponseMessage(message.CorrelationToken);
                message.BatchOperationType = BatchOperationType.CancelRequestForResponse;
            }
            catch (COMException e)
            {
                // Failed - Assume that this is not expected to fail (as documentation doesn't state) so no corrective action needed
                TraceHelper.WriteEntry(TraceLevel.Error, ResourceConstants.BizTalkBatchMethodFailure, "CancelResponseMessage", this.BatchCookie, e.ErrorCode, e.Message);
            }

            if (this._cancelResponseMessages == null)
            {
                // Create array
                this._cancelResponseMessages = new List<BatchMessage>();
            }

            // Add to array
            this._cancelResponseMessages.Add(message);

            // Set count and message order
            this._messageCount++;
            message.MessageOrder = this._messageCount;
        }

        /// <summary>
        ///     This method builds and submits the batch to BizTalk.
        /// </summary>
        public void SubmitBatch()
        {
            // Submit batch
            this.TransportBatch.Done(null);

            // Clean up batch
            if (Marshal.IsComObject(this.TransportBatch))
            {
                while (0 < Marshal.ReleaseComObject(this.TransportBatch));
                GC.SuppressFinalize(this.TransportBatch);
                this._batch = null;
            }

            // Wait on ordered event if this is an ordered batch
            if (this.OrderedBatch)
            {
                this._orderedEvent.WaitOne();
            }
        }

        /// <summary>
        ///     This method handles batch failures by constructing a new batch to resubmit based on the type of
        ///     failures encountered.
        /// </summary>
        /// <remarks>
        ///     At the top level (the original batch), if failure batches extend more than
        ///     X number (the depth level), the process aborts.  In this instance it is necessary to save off the
        ///     batch.  This method is called by the IBTBatchCallback instance which is used by BizTalk to inform
        ///     the adapter of the status of a submitted batch.
        /// 
        ///     If the TopLevelBatch property is null then this instance is the top level batch.
        /// </remarks>
        public void HandleBatchFailure(short opCount, BTBatchOperationStatus[] operationStatus)
        {
            // Are we a child failure batch?
            if (this.TopLevelBatch != null)
            {
                // Check that the depth hasn't reached a certain limit
                if (this.TopLevelBatch.FailureDepth >= this.RetryCount)
                {
                    string message = TraceHelper.FormatEntry(ResourceConstants.ReceiveReachedMaximumFailureDepth);
                    TraceHelper.WriteRawEntry(TraceLevel.Error, message);

                    // Throw exception that failure depth has been exceeded
                    throw new Exception(message);
                }
            }

            // Submit batch, do this a number of times if we fail
            bool batchSuccess = false;
            for (int i = 0; i < this.RetryCount; i++)
            {
                try
                {
                    // Create a new batch for the new operations
                    this._failureBatch = new Batch(this.Adapter);
                    this.FailureBatch.RetryCount = this.RetryCount;
                    this.FailureBatch.RetryInterval = this.RetryInterval;
                    this.FailureBatch.OrderedBatch = this.OrderedBatch;
                    this.FailureBatch.TopLevelBatch = (this.TopLevelBatch == null ? this : this.TopLevelBatch);

                    // Handle resubmission operations based on state
                    this.ResubmitBatch(opCount, operationStatus);

                    // Submit (it will wait if it is ordered)
                    this.FailureBatch.SubmitBatch();

                    // If we get this far, submission worked so we can set flag and increment failure depth counter
                    batchSuccess = true;
                    if (this.TopLevelBatch == null)
                    {
                        // We are the top level batch so increment failure depth here
                        this.FailureDepth++;
                    }
                    else
                    {
                        // We are a child failure batch so increment our top level batch
                        this.TopLevelBatch.FailureDepth++;
                    }
                    break;
                }
                catch (Exception e)
                {
                    // Trace
                    TraceHelper.WriteEntry(TraceLevel.Error, ResourceConstants.ReceiveFailedSubmittingBatchRetrying, this.FailureBatch.BatchCookie, e.Message);

                    // Wait interval
                    Thread.Sleep(this.RetryInterval * 1000);
                }
            }

            if (!batchSuccess)
            {
                string errorMessage = TraceHelper.FormatEntry(ResourceConstants.ReceiveFailedSubmittingBatch, this.BatchCookie);
                TraceHelper.WriteRawEntry(TraceLevel.Error, errorMessage);
                throw new Exception(errorMessage);
            }
        }

        /// <summary>
        ///     This method gets a failure batch from this or child failure batches that matches the batch cookie
        ///     or null if it doesn't exist.
        /// </summary>
        public Batch GetFailureBatch(string batchCookie)
        {
            if (this.FailureBatch != null && !this.IsDisposed)
            {
                if (this.FailureBatch.BatchCookie == batchCookie)
                {
                    return (this.FailureBatch);
                }
                else
                {
                    return (this.FailureBatch.GetFailureBatch(batchCookie));
                }
            }
            else
            {
                return (null);
            }
        }

        /// <summary>
        ///     This method removes a failure batch from this or child failure batches that matches the batch cookie.
        /// </summary>
        public void RemoveFailureBatch(string batchCookie)
        {
            if (this.FailureBatch != null && !this.IsDisposed)
            {
                if (this.FailureBatch.BatchCookie == batchCookie)
                {
                    this._failureBatch.Dispose();
                    this._failureBatch = null;
                }
                else
                {
                    this.FailureBatch.RemoveFailureBatch(batchCookie);
                }
            }
        }

        /// <summary>
        ///     This method saves the messages in the batch to the dead message folder.
        /// </summary>
        public void Save()
        {
            // Submit Messages
            if (this._submitMessages != null)
            {
                // Save mesage
                foreach (BatchMessage message in this._submitMessages)
                {
                    DeadMessage.WriteStreamToDisk(this.Adapter, message.Message.BodyPart.Data);
                }
            }

            // Delete Messages
            if (this._deleteMessages != null)
            {
                // Do nothing
            }

            // Resubmit Messages
            if (this._resubmitMessages != null)
            {
                // Do nothing
            }

            // Suspended Messages
            if (this._moveToSuspendQMessages != null)
            {
                // Save mesage
                foreach (BatchMessage message in this._moveToSuspendQMessages)
                {
                    DeadMessage.WriteStreamToDisk(this.Adapter, message.Message.BodyPart.Data);
                }
            }

            // Move to Next Transport Messages
            if (this._moveToNextTransportMessages != null)
            {
                // Do nothing
            }

            // Submit Request (as part of a pair) Messages
            if (this._submitRequestMessages != null)
            {
                // Save mesage
                foreach (BatchMessage message in this._submitRequestMessages)
                {
                    DeadMessage.WriteStreamToDisk(this.Adapter, message.Message.BodyPart.Data);
                }
            }

            // Submit Response Messages
            if (this._submitResponseMessages != null)
            {
                // Save mesage
                foreach (BatchMessage message in this._submitResponseMessages)
                {
                    DeadMessage.WriteStreamToDisk(this.Adapter, message.Message.BodyPart.Data);
                }
            }

            // Cancel Response Messages
            if (this._cancelResponseMessages != null)
            {
                // Do nothing
            }
        }

        #endregion

        #region Private Structures

        /// <summary>
        ///     This structure holds the information necessary to allow a resubmission of a batch message.
        /// </summary>
        private struct BatchMessageResubmitOperation
        {
            #region Public Instance Members

            /// <summary>
            ///     This member defines the message that is to be resubmitted.
            /// </summary>
            public BatchMessage Message;

            /// <summary>
            ///     This members specifies the original operation status code (HRESULT).
            /// </summary>
            public int OperationStatus;

            #endregion

            #region Constructor

            /// <summary>
            ///     This constructor initializes the structure.
            /// </summary>
            public BatchMessageResubmitOperation(BatchMessage message, int operationStatus)
            {
                // Validate
                if (message == null) throw new ArgumentNullException("message");

                // Initialize
                this.Message = message;
                this.OperationStatus = operationStatus;
            }

            #endregion
        }

        #endregion

        #region Private Instance Methods

        /// <summary>
        ///     This method resubmits the failed batch.
        /// </summary>
        private void ResubmitBatch(short opCount, BTBatchOperationStatus[] operationStatus)
        {
            // Set up an array for a batch that follows the original order
            BatchMessageResubmitOperation[] orderedBatch = new BatchMessageResubmitOperation[opCount];

            // Work out which operations worked and failed
            foreach (BTBatchOperationStatus opStatus in operationStatus)
            {
                // Variables
                BatchMessage message = null;

                // Go through each operation type
                switch (opStatus.OperationType)
                {
                    case BatchOperationType.Submit:

                        for (int i = 0; i < opStatus.MessageCount; i++)
                        {
                            // Get message from array
                            if (this._isSubmitResponse)
                            {
                                // Get message from array
                                message = this._submitResponseMessages[i];
                            }
                            else
                            {
                                // Get message from array
                                message = this._submitMessages[i];
                            }

                            // Add new structure to correct place in array
                            orderedBatch[message.MessageOrder - 1] = new BatchMessageResubmitOperation(message, opStatus.MessageStatus[i]);
                        }
                        break;

                    case BatchOperationType.SubmitRequest:

                        for (int i = 0; i < opStatus.MessageCount; i++)
                        {
                            // Get message from array
                            message = this._submitRequestMessages[i];

                            // Add new structure to correct place in array
                            orderedBatch[message.MessageOrder - 1] = new BatchMessageResubmitOperation(message, opStatus.MessageStatus[i]);
                        }
                        break;

                    case BatchOperationType.Delete:

                        for (int i = 0; i < opStatus.MessageCount; i++)
                        {
                            // Get message from array
                            message = this._deleteMessages[i];

                            // Add new structure to correct place in array
                            orderedBatch[message.MessageOrder - 1] = new BatchMessageResubmitOperation(message, opStatus.MessageStatus[i]);
                        }
                        break;

                    case BatchOperationType.Resubmit:

                        for (int i = 0; i < opStatus.MessageCount; i++)
                        {
                            // Get message from array
                            message = this._resubmitMessages[i];

                            // Add new structure to correct place in array
                            orderedBatch[message.MessageOrder - 1] = new BatchMessageResubmitOperation(message, opStatus.MessageStatus[i]);
                        }
                        break;

                    case BatchOperationType.MoveToSuspendQ:

                        for (int i = 0; i < opStatus.MessageCount; i++)
                        {
                            // Get message from array
                            message = this._moveToSuspendQMessages[i];

                            // Add new structure to correct place in array
                            orderedBatch[message.MessageOrder - 1] = new BatchMessageResubmitOperation(message, opStatus.MessageStatus[i]);
                        }
                        break;

                    case BatchOperationType.MoveToNextTransport:

                        for (int i = 0; i < opStatus.MessageCount; i++)
                        {
                            // Get message from array
                            message = this._moveToNextTransportMessages[i];

                            // Add new structure to correct place in array
                            orderedBatch[message.MessageOrder - 1] = new BatchMessageResubmitOperation(message, opStatus.MessageStatus[i]);
                        }
                        break;

                    case BatchOperationType.CancelRequestForResponse:

                        for (int i = 0; i < opStatus.MessageCount; i++)
                        {
                            // Get message from array
                            message = this._cancelResponseMessages[i];

                            // Add new structure to correct place in array
                            orderedBatch[message.MessageOrder - 1] = new BatchMessageResubmitOperation(message, opStatus.MessageStatus[i]);
                        }
                        break;
                }
            }

            // Submit batch
            foreach (BatchMessageResubmitOperation op in orderedBatch)
            {
                // Was operation successful?
                if (op.OperationStatus < 0)
                {
                    // Failed
                    string message = TraceHelper.FormatEntry(ResourceConstants.BizTalkBatchOperationFailure, op.Message.BatchOperationType, op.OperationStatus);

                    if (this.Adapter.TraceSwitch.TraceWarning)
                        TraceHelper.WriteRawEntry(TraceLevel.Warning, message);

                    // Failed
                    if (op.Message.BatchOperationType == BatchOperationType.Resubmit)
                    {
                        // Resubmit failed - move the message to the next transport
                        op.Message.BatchOperationType = BatchOperationType.MoveToNextTransport;

                        // Move to next transport
                        this.FailureBatch.MoveToBackupTransport(op.Message);
                    }
                    else
                    {
                        // Submit message
                        op.Message.BatchOperationType = BatchOperationType.MoveToSuspendQ;

                        // Suspend message
                        op.Message.MessageException = new Exception(message);
                        this.FailureBatch.MoveToSuspendQ(op.Message);
                    }
                }
                else
                {
                    // Success
                    string message = TraceHelper.FormatEntry(ResourceConstants.BizTalkBatchOperationSuccess, op.Message.BatchOperationType);

                    if (this.Adapter.TraceSwitch.TraceWarning)
                        TraceHelper.WriteRawEntry(TraceLevel.Warning, message);

                    // Submit again with same operation
                    switch (op.Message.BatchOperationType)
                    {
                        case BatchOperationType.Submit :

                            if (this._isSubmitResponse)
                            {
                                // Submit response
                                this.FailureBatch.SubmitResponseMessage(op.Message);
                            }
                            else
                            {
                                // Submit
                                this.FailureBatch.SubmitMessage(op.Message);
                            }
                            break;

                        case BatchOperationType.SubmitRequest :

                            // Submit request
                            this.FailureBatch.SubmitRequestMessage(op.Message);
                            break;

                        case BatchOperationType.Resubmit :

                            // Resubmit
                            this.FailureBatch.ResubmitMessage(op.Message);
                            break;

                        case BatchOperationType.Delete :

                            // Delete
                            this.FailureBatch.DeleteMessage(op.Message);
                            break;

                        case BatchOperationType.MoveToSuspendQ :

                            // Move to suspend queue
                            op.Message.MessageException = new Exception(message);
                            this.FailureBatch.MoveToSuspendQ(op.Message);
                            break;

                        case BatchOperationType.MoveToNextTransport :

                            // Move to next transport
                            this.FailureBatch.MoveToBackupTransport(op.Message);
                            break;

                        case BatchOperationType.CancelRequestForResponse :

                            // Cancel response
                            this.FailureBatch.CancelResponseMessage(op.Message);
                            break;
                    }
                }
            }
        }

        /// <summary>
        ///     This method provides a private implementation which a flag that specifies whether we
        ///     are manually disposing of the object, or is the CLR disposing of it for us.
        /// </summary>
        private void Dispose(bool disposing)
        {
            // Only dispose if we are not already
            if (!this.IsDisposed)
            {
                // If we have been GC collected, the CLR may have already disposed of other object references
                // so it may not be possible to clean them up.  If we are manually disposed, we can dispose
                // of our objects.
                if (disposing)
                {
                    // Release batch COM references
                    if (this.TransportBatch != null)
                    {
                        if (Marshal.IsComObject(this.TransportBatch))
                        {
                            while (0 < Marshal.ReleaseComObject(this.TransportBatch)) ;
                            GC.SuppressFinalize(this.TransportBatch);
                            this._batch = null;
                        }
                    }
                }

                // Set flag
                this._isDisposed = true;
            }
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        ///     This method disposes of the object manually.
        /// </summary>
        public void Dispose()
        {
            // Dispose
            this.Dispose(true);

            // Suppress finalize as we have already cleaned it up
            GC.SuppressFinalize(this);
        }

        #endregion
    }
}
